<!-- @format -->

<template>
  <div :class="dropdownClass">
    <!-- 触发器 -->
    <div
      class="comp-dropdown__trigger"
      @click="handleTriggerClick"
      @mouseenter="triggerHoverShow && handleTriggerEnter"
      @mouseleave="triggerHoverShow && handleTriggerLeave"
      ref="triggerRef"
    >
      <slot name="trigger">
        <button class="comp-dropdown__default-trigger">
          {{ label }}
          <span class="comp-dropdown__arrow" :class="{ 'is-active': visible }">
            <svg viewBox="0 0 1024 1024" width="12" height="12">
              <path
                d="M831.872 340.864L512 652.672 192.128 340.864a29.568 29.568 0 0 0-41.728 0 29.568 29.568 0 0 0 0 41.728l340.288 332.8a29.568 29.568 0 0 0 41.728 0l340.288-332.8a29.568 29.568 0 0 0 0-41.728 29.568 29.568 0 0 0-41.728 0z"
                fill="currentColor"
              ></path>
            </svg>
          </span>
        </button>
      </slot>
    </div>

    <!-- 下拉菜单 -->
    <transition name="comp-dropdown-fade">
      <div
        v-show="visible"
        class="comp-dropdown__menu"
        :style="menuStyle"
        ref="menuRef"
        @mouseenter="hideOnClick && handleMenuEnter"
        @mouseleave="hideOnClick && handleMenuLeave"
      >
        <slot name="dropdown"></slot>
      </div>
    </transition>
  </div>
</template>

<script setup lang="ts">
  import {
    ref,
    computed,
    onMounted,
    onBeforeUnmount,
    nextTick,
    watch,
    provide,
  } from 'vue'

  // 定义 props
  interface DropdownProps {
    placement?:
      | 'top'
      | 'top-start'
      | 'top-end'
      | 'bottom'
      | 'bottom-start'
      | 'bottom-end'
    trigger?: 'hover' | 'click'
    disabled?: boolean
    hideOnClick?: boolean
    showTimeout?: number
    hideTimeout?: number
    tabindex?: number | string
    label?: string
    maxHeight?: number | string
    offset?: number
    theme?: 'light' | 'dark'
    size?: 'large' | 'default' | 'small'
  }

  const props = withDefaults(defineProps<DropdownProps>(), {
    placement: 'bottom',
    trigger: 'hover',
    disabled: false,
    hideOnClick: true,
    showTimeout: 150,
    hideTimeout: 150,
    tabindex: 0,
    label: '下拉菜单',
    maxHeight: 'auto',
    offset: 5,
    theme: 'light',
    size: 'default',
  })

  // 定义 emits
  const emit = defineEmits<{
    'visible-change': [visible: boolean]
    command: [command: string | number | object, instance: any]
  }>()

  // 状态变量
  const visible = ref(false)
  const triggerRef = ref<HTMLElement | null>(null)
  const menuRef = ref<HTMLElement | null>(null)
  const timeout = ref<NodeJS.Timeout | null>(null)

  // 计算属性
  const triggerHoverShow = computed(
    () => props.trigger === 'hover' && !props.disabled,
  )
  const menuStyle = computed(() => {
    return {
      maxHeight:
        typeof props.maxHeight === 'number'
          ? `${props.maxHeight}px`
          : props.maxHeight,
    }
  })

  const dropdownClass = computed(() => [
    'comp-dropdown',
    {
      'is-disabled': props.disabled,
      [`comp-dropdown--${props.theme}`]: props.theme,
      [`comp-dropdown--${props.size}`]: props.size,
    },
  ])

  // 方法
  const clearTimer = () => {
    if (timeout.value) {
      clearTimeout(timeout.value)
      timeout.value = null
    }
  }

  const show = () => {
    if (props.disabled) return
    clearTimer()
    timeout.value = setTimeout(() => {
      visible.value = true
      emit('visible-change', true)
      nextTick(() => {
        updateMenuPosition()
      })
    }, props.showTimeout)
  }

  const hide = () => {
    if (props.disabled) return
    clearTimer()
    timeout.value = setTimeout(() => {
      visible.value = false
      emit('visible-change', false)
    }, props.hideTimeout)
  }

  const handleTriggerClick = () => {
    if (props.disabled) return
    if (props.trigger === 'click') {
      visible.value ? hide() : show()
    }
  }

  const handleTriggerEnter = () => {
    if (props.disabled) return
    if (props.trigger === 'hover') {
      show()
    }
  }

  const handleTriggerLeave = () => {
    if (props.disabled) return
    if (props.trigger === 'hover') {
      hide()
    }
  }

  const handleMenuEnter = () => {
    clearTimer()
  }

  const handleMenuLeave = () => {
    hide()
  }

  const handleClickOutside = (event: MouseEvent) => {
    if (!visible.value) return
    const target = event.target as HTMLElement
    if (triggerRef.value && triggerRef.value.contains(target)) return
    if (menuRef.value && menuRef.value.contains(target)) return
    hide()
  }

  const handleMenuItemClick = (
    command: string | number | object,
    instance: any,
  ) => {
    emit('command', command, instance)
    if (props.hideOnClick) {
      hide()
    }
  }

  // 提供给子组件的方法
  provide('handleMenuItemClick', handleMenuItemClick)

  const updateMenuPosition = () => {
    if (!menuRef.value || !triggerRef.value) return

    const { placement, offset } = props

    // 重置内联样式，让CSS样式生效
    menuRef.value.style.top = ''
    menuRef.value.style.bottom = ''
    menuRef.value.style.left = ''
    menuRef.value.style.right = ''
    menuRef.value.style.transform = ''

    // 根据placement设置不同的定位
    if (placement.startsWith('top')) {
      menuRef.value.style.top = 'auto'
      menuRef.value.style.bottom = `calc(100% + ${offset}px)`
    } else {
      menuRef.value.style.top = `calc(100% + ${offset}px)`
      menuRef.value.style.bottom = 'auto'
    }

    // 水平方向定位
    if (placement.endsWith('start')) {
      menuRef.value.style.left = '0'
      menuRef.value.style.right = 'auto'
    } else if (placement.endsWith('end')) {
      menuRef.value.style.left = 'auto'
      menuRef.value.style.right = '0'
    } else {
      menuRef.value.style.left = '50%'
      menuRef.value.style.right = 'auto'
      menuRef.value.style.transform = 'translateX(-50%)'
    }
  }

  // 暴露方法给父组件
  defineExpose({
    show,
    hide,
    handleMenuItemClick,
  })

  // 生命周期钩子
  onMounted(() => {
    document.addEventListener('click', handleClickOutside)
  })

  onBeforeUnmount(() => {
    document.removeEventListener('click', handleClickOutside)
    clearTimer()
  })

  // 监听 visible 变化
  watch(visible, (val) => {
    if (val) {
      nextTick(() => {
        updateMenuPosition()
      })
    }
  })
</script>

<style scoped>
  .comp-dropdown {
    display: inline-block;
    position: relative;
    color: #606266;
    font-size: 14px;
  }

  .comp-dropdown.is-disabled .comp-dropdown__trigger {
    cursor: not-allowed;
    opacity: 0.7;
  }

  .comp-dropdown__trigger {
    cursor: pointer;
    outline: none;
  }

  .comp-dropdown__default-trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 16px;
    font-size: 14px;
    background-color: #fff;
    border: 1px solid #dcdfe6;
    border-radius: 4px;
    cursor: pointer;
    transition: all 0.3s;
    min-width: 120px;
    box-sizing: border-box;
    color: #606266;
    outline: none;
    font-weight: 500;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  }

  .comp-dropdown__default-trigger:hover {
    border-color: #409eff;
    color: #409eff;
  }

  .comp-dropdown__arrow {
    margin-left: 8px;
    transition: transform 0.3s;
    font-size: 12px;
    display: inline-flex;
    align-items: center;
  }

  .comp-dropdown__arrow.is-active {
    transform: rotate(180deg);
  }

  .comp-dropdown__menu {
    position: absolute;
    z-index: 1000;
    padding: 5px 0;
    margin: 0;
    background-color: #fff;
    border: 1px solid #e4e7ed;
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    min-width: 100%;
    overflow-y: auto;
    max-height: 300px;
    white-space: nowrap;
  }

  /* 尺寸变体 */
  .comp-dropdown--large .comp-dropdown__default-trigger {
    padding: 12px 20px;
    font-size: 16px;
    min-width: 140px;
  }

  .comp-dropdown--small .comp-dropdown__default-trigger {
    padding: 6px 12px;
    font-size: 12px;
    min-width: 100px;
  }

  /* 主题变体 */
  .comp-dropdown--dark .comp-dropdown__default-trigger {
    background-color: #303133;
    border-color: #303133;
    color: #fff;
  }

  .comp-dropdown--dark .comp-dropdown__default-trigger:hover {
    background-color: #409eff;
    border-color: #409eff;
  }

  .comp-dropdown--dark .comp-dropdown__menu {
    background-color: #303133;
    border-color: #303133;
    color: #fff;
  }

  .comp-dropdown-fade-enter-active,
  .comp-dropdown-fade-leave-active {
    transition:
      opacity 0.3s,
      transform 0.3s;
  }

  .comp-dropdown-fade-enter-from,
  .comp-dropdown-fade-leave-to {
    opacity: 0;
    transform: translateY(-8px);
  }
</style>
